<?php


namespace App\Repositories;


use App\Commons\Utils\ModelTools\BaseEloquentBuilder;
use App\Commons\Utils\ModelTools\BaseModel;
use App\Commons\Utils\ModelTools\PaginateQueryModel;
use Illuminate\Http\Request;

abstract class BaseRepository {
    /**
     * 对应模型类名 $eloquentClassName = \App\Eloquent\Sys\EloquentAppInfo::class
     * @var string
     */
    public $eloquentClassName;
    
    
    /**
     * 关联模型对象
     * 返回值为 BaseModel或者null,这里为了代码提示添加\Illuminate\Database\Eloquent\Builder
     * @var BaseModel|null|\Illuminate\Database\Eloquent\Builder
     */
    protected $_eloquent;
    
    
    /**
     * 子类实例集合，键：类名，值：子类对象
     * @var static
     */
    protected static $_instance = [];
    
    /**
     * BaseRepository constructor.
     *
     * @param bool $isInstallEloquent
     */
    public function __construct($isInstallEloquent = true) {
        $isInstallEloquent && $this->getEloquent();
    }
    
    
    /**
     * 获取绑定模型对象
     *
     * @param bool $isRefresh 是否重新获取新实例
     *
     * @return BaseModel|null
     */
    public function getEloquent($isRefresh = false) {
        if (!$this->_eloquent || $isRefresh) {
            $this->_eloquent = $this->makeEloquent();
        }
        
        return $this->_eloquent;
    }
    
    /**
     * 新建关联模型对象
     * 返回值为 BaseModel或者null,这里为了代码提示添加\Illuminate\Database\Eloquent\Builder
     * @return null|BaseModel|\Illuminate\Database\Eloquent\Builder
     */
    public function makeEloquent(array $parameters = []) {
        return $this->eloquentClassName ? app()->make($this->eloquentClassName, $parameters) : null;
    }
    
    /**
     * 获取实例
     *
     * @param bool $isRefresh 是否重新获取新实例
     * @param bool $isInstallEloquent 是否实例化关联模型对象
     *
     * @return static
     */
    public static function getInstance($isRefresh = false, $isInstallEloquent = true) {
        if (!isset(self::$_instance[static::class]) || $isRefresh) {
            self::$_instance[static::class] = new static($isInstallEloquent);
        }
        
        return self::$_instance[static::class];
    }
    
    /**
     * get请求分页信息时，过滤信息请求参数名称
     */
    const GET_METHOD_PAGE_FILTER_INFO_PARAM_NAME = 'page_filter';
    
    /**
     * 获取后台分页字段过滤查询结果
     *
     * @param null   $queryExtDealFn
     * @param array  $orderByList
     * @param bool   $isNoDelete
     * @param string $noDeleteScopeSuffix
     * @param null   $request
     * @param null   $condition
     * @param null   $pageQueryModel
     *
     * @return mixed
     * e.g,
     * [
     *    'total_count' => 10,
     *    'list'        => new \Illuminate\Database\Eloquent\Collection([...]),
     * ];
     */
    public function queryByConditionAndPage($queryExtDealFn = null, $orderByList = null, $isNoDelete = true, $noDeleteScopeSuffix = 'notDelete', $request = null, $condition = null, $pageQueryModel = null) {
        $orderByList = $orderByList ?: ['id' => 'desc'];
        /** @var Request $request */
        $request = $request ?: app('request');
        
        //分页参数获取数据处理
        $pageInfo = [];
        if ($request->getMethod() == Request::METHOD_GET) {
            $pageFilterJson = $request->get(self::GET_METHOD_PAGE_FILTER_INFO_PARAM_NAME);
            $pageFilterInfo = $pageFilterJson ? json_decode($pageFilterJson, true) : [];
            $pageFilterInfo = is_array($pageFilterInfo) ? $pageFilterInfo : [];
            $pageInfo       = $pageFilterInfo['page_info'] ?? [];
            $condition      = $condition ?: ($pageFilterInfo['conditions'] ?? []);
        } else {
            $postPageInfo = $request->get('page_info');
            $postPageInfo && ($pageInfo = $postPageInfo);
            $condition = $condition ?: ($request->get('conditions') ?? []);
        }
        
        $pageQueryModel = $pageQueryModel ?: new PaginateQueryModel($pageInfo);
        
        //额外查询条件添加
        $query = $this->getEloquent()->newQuery();
        is_callable($queryExtDealFn) && call_user_func_array($queryExtDealFn, [$query]);
        
        //添加未删除条目查询条件
        if ($isNoDelete) {
            $noDeleteScope = 'scope' . ucfirst($noDeleteScopeSuffix);
            method_exists($this->getEloquent(), $noDeleteScope) && $query->$noDeleteScopeSuffix();
        }
        
        //排序处理
        foreach ($orderByList as $orderByField => $orderByVal) {
            call_user_func_array([$query, 'orderBy'], [$orderByField, $orderByVal]);
        }
        
        return $query->filterQuery($condition)->jsonPaginate($pageQueryModel);
    }
    
    /**
     * 获取查询构造器
     * @return \Illuminate\Database\Eloquent\Builder|BaseEloquentBuilder|null
     */
    public function getQuery() {
        return ($eloquent = $this->getEloquent()) ? $eloquent->newQuery() : null;
    }
    
    /**
     * 根据id获取模型
     *
     * @param int    $id
     * @param null   $throwException 如果不为null,则在不存在该条目时则抛出异常
     * @param null   $queryExtDealFn 额外处理查询构造器函数
     * @param bool   $isNoDelete 是否添加未删除查询条件
     * @param string $noDeleteScopeSuffix 未删除的 scope函数后缀
     *
     * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Builder[]|\Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Model|null
     * @throws \Exception
     */
    public function findItemElById(int $id, $throwException = null, $queryExtDealFn = null, $isNoDelete = true, $noDeleteScopeSuffix = 'notDelete') {
        $query = $this->getEloquent()->newQuery();
        is_callable($queryExtDealFn) && call_user_func_array($queryExtDealFn, [$query]);
        
        //添加未删除条目查询条件
        if ($isNoDelete) {
            $noDeleteScope = 'scope' . ucfirst($noDeleteScopeSuffix);
            method_exists($this->getEloquent(), $noDeleteScope) && $query->$noDeleteScopeSuffix();
        }
        
        $itemEl = $query->find($id);
        
        //如果为找到条目，则根据是否传递了异常对象决定是否直接抛出异常
        if (!$itemEl && $throwException) {
            /** @var \Exception $throwException */
            throw $throwException;
        }
        
        return $itemEl;
    }
    
    const PAGE_QUERY_PAGE_PARAM_NAME      = 'page';
    const PAGE_QUERY_PAGE_SIZE_PARAM_NAME = 'page_size';
    
    /**
     * 获取分页查询构造器
     * @return \Illuminate\Database\Eloquent\Builder|BaseEloquentBuilder|null
     */
    public function getPageQuery() {
        $query = $this->getQuery();
        if ($query) {
            $page     = intval(request(self::PAGE_QUERY_PAGE_PARAM_NAME)) ?: 1;
            $pageSize = intval(request(self::PAGE_QUERY_PAGE_SIZE_PARAM_NAME)) ?: 10;
            $offset   = ($page - 1) * $pageSize;
            
            $query->offset($offset)
                ->limit($pageSize);
        }
        
        return $query;
    }
    
    /**
     * 根据条件获取数据
     *
     * @param array $conditionList
     * @param null  $extDealQueryFn
     * @param bool  $isGetOne
     * @param bool   $isNoDelete
     * @param string $noDeleteScopeSuffix
     *
     * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Builder[]|\Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Model|object|null
     */
    public function getItemByConditions(array $conditionList, $extDealQueryFn = null, $isGetOne = false, $isNoDelete = true, $noDeleteScopeSuffix = 'notDelete') {
        $query = $this->getEloquent()->newQuery();
        
        foreach ($conditionList as $column => $condition) {
            $whereCondition = is_array($condition) ? 'whereIn' : 'where';
            $query->$whereCondition($column, $condition);
        }
    
        //添加未删除条目查询条件
        if ($isNoDelete) {
            $noDeleteScope = 'scope' . ucfirst($noDeleteScopeSuffix);
            method_exists($this->getEloquent(), $noDeleteScope) && $query->$noDeleteScopeSuffix();
        }
        
        //对查询构造器的额外处理
        $extDealQueryFn && is_callable($extDealQueryFn) && call_user_func_array($extDealQueryFn, [$query]);
        
        $itemEl = $isGetOne ? $query->first() : $query->get();
        
        return $itemEl;
    }
    
    /**
     * @param array         $dataInfo
     * @param null          $fillAble
     * @param callable|null $extDealFn
     *
     * @return \Illuminate\Database\Eloquent\Model
     */
    public function store(array $dataInfo, $fillAble = null, $extDealFn = null) {
        $newEl = $this->makeEloquent()->fillable($fillAble ?: array_keys($dataInfo))->fill($dataInfo);
        
        is_callable($extDealFn) && call_user_func_array($extDealFn, [$newEl]);
        
        $newEl->save();
        
        return $newEl;
    }
    
    /**
     * @param       $id
     * @param array $dataInfo
     * @param array $fillAble
     *
     * @return mixed|\Illuminate\Database\Eloquent\Model
     * @throws \Exception
     */
    public function update($id, array $dataInfo, $fillAble = null, $notFoundThrowException = null, $extDealFn = null, $queryExtDealFn = null, $isNoDelete = true, $noDeleteScopeSuffix = 'notDelete') {
        $itemEl = $this->findItemElById($id, $notFoundThrowException, $queryExtDealFn, $isNoDelete, $noDeleteScopeSuffix);
        
        is_callable($extDealFn) && call_user_func_array($extDealFn, [$itemEl]);
        
        $itemEl && $itemEl->fillable(is_null($fillAble) ? array_keys($dataInfo) : $fillAble)->fill($dataInfo)->save();
        
        return $itemEl;
    }
    
    /**
     * @param $id
     *
     * @return mixed|\Illuminate\Database\Eloquent\Model
     * @throws \Exception
     */
    public function delete($id, $extDealFn = null, $notFoundThrowException = null, $queryExtDealFn = null, $isNoDelete = true, $noDeleteScopeSuffix = 'notDelete') {
        $itemEl = $this->findItemElById($id, $notFoundThrowException, $queryExtDealFn, $isNoDelete, $noDeleteScopeSuffix);
        
        is_callable($extDealFn) && call_user_func_array($extDealFn, [$itemEl]);
        
        if ($itemEl) {
            $itemEl->deleted = \App\Commons\Utils\ModelTools\BaseModel::DELETED_YES;
            $itemEl->save();
        }
        
        return $itemEl;
    }
    
    /**
     * @param $ids
     */
    public function destroy($ids) {
        $this->getEloquent()->destroy($ids);
    }

    /**
     *  远程搜索返回列表
     *
     * @param $queryOptions
     * @param null $selectField
     * @param null $queryExtDealFn
     * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Builder[]|\Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Model|\Illuminate\Database\Query\Builder|\Illuminate\Support\Collection|object|null
     */
    public function getItemsByRemoteSearch($queryOptions, $selectField = null, $queryExtDealFn = null) {
        $query = $this->_eloquent->newQuery()->select($selectField);

        foreach ($queryOptions as $field => $value) {
            $query->where($field, 'like', "%{$value}%");
        }

        //额外查询条件添加
        is_callable($queryExtDealFn) && call_user_func_array($queryExtDealFn, [$query]);

        return $query->get();
    }
}